﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Text;

using System.Security.Cryptography;
using System.IO;

namespace SSN.WebFramework
{
    /// <summary>
    /// Represents a manager that encrypts and decrypters query encoded parameters.
    /// </summary>
    public class ParameterManager
    {
        private const int key_length = 8;
        private static readonly byte[] iv = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
        private string _key = null;
        private NameValueCollection _params = new NameValueCollection();
        private SecurityManager _secmgr = null;
        private HttpServerUtility _server = null;

        /// <summary>
        /// Creates and initializes a ParameterManager object.
        /// </summary>
        /// <param name="secmgr">Current SecurityManager.</param>
        /// <param name="srvr">Current HttpServerUtility of the page.</param>
        /// <param name="querystring">Current QueryString from the Request.</param>
        public ParameterManager(SecurityManager secmgr, HttpServerUtility srvr, NameValueCollection querystring)
        {
            _secmgr = secmgr;
            _server = srvr;

            Initialize(querystring);
        }



        /// <summary>
        /// Creates and initializes a ParameterManager object.
        /// </summary>
        /// <param name="secmgr">Current SecurityManager.</param>
        /// <param name="srvr">Current HttpServerUtility of the page.</param>
        /// <param name="querystring">Current string from the Request's query string.</param>
        public ParameterManager(SecurityManager secmgr, HttpServerUtility srvr, string querystring)
        {
            _secmgr = secmgr;
            _server = srvr;

            if (querystring.Length > 1)
            {
                NameValueCollection nvc = new NameValueCollection();
                if (querystring.StartsWith("?"))
                    querystring = querystring.Substring(1);

                foreach (string kvp in querystring.Split('&'))
                {
                    if (kvp.Length > 0 && kvp.Contains('='))
                    {
                        string[] sar = kvp.Split('=');
                        nvc.Add(srvr.UrlDecode(sar[0]), srvr.UrlDecode(sar[1]));
                    }
                }

                Initialize(nvc);
            }
        }

        /// <summary>
        /// Initializes the manager.
        /// </summary>
        /// <param name="querystring"></param>
        private void Initialize(NameValueCollection querystring)
        {
            if (!EncryptParameters)
            {
                _params = querystring;
            }
            else
            {
                if (_secmgr.IsLoggedIn)
                {
                    if (!BuildEncryptionKeys())
                        throw new ApplicationException("Unable to get encryption key");

                    string enc_str = querystring["ecv"];
                    if (enc_str != null)
                    {
                        _params = DeCrypt(enc_str);
                    }
                }
            }
        }


        #region property stuff
        /// <summary>
        /// Indicates whether the site is set to encrypt the query string parameters.
        /// </summary>
        public bool EncryptParameters
        {
            get
            {
                return WebSettings.EncryptParameters;
            }
        }


        /// <summary>
        /// Gets the value of the parameter, if it exists. If it does not exist, a blank string is returned.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>Value associated with the key. If the key does not exist, an empty string is returned.</returns>
        public string this[string key]
        {
            get
            {
                string[] vals = _params.GetValues(key);
                if (vals == null || vals.Length == 0)
                    return "";
                return vals[0];
            }
        }

        /// <summary>
        /// Returns all the keys given in the query string.
        /// </summary>
        public List<string> Keys
        {
            get
            {
                return new List<string>(_params.AllKeys);
            }
        }
        #endregion

        #region query string encryption/decryption
        /// <summary>
        /// Encrypts a query string. String can either include or leave out the ? at the beginning. Value returned will be prepended with ?ecv
        /// </summary>
        /// <param name="queryString">The query string to encrypt.</param>
        /// <returns>The query string encrypted.</returns>
        public string EncryptQueryString(string queryString)
        {
            if (!EncryptParameters)
            {
                return queryString;
            }

            if (!queryString.StartsWith("?"))
            {
                queryString = queryString.Substring(1);
            }

            try
            {
                byte[] bgkey = Encoding.UTF8.GetBytes(GetEncryptionKey());

                using (DESCryptoServiceProvider des1 = new DESCryptoServiceProvider())
                {
                    byte[] input_arry = Encoding.UTF8.GetBytes(queryString);

                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, des1.CreateEncryptor(bgkey, iv), CryptoStreamMode.Write))
                        {
                            cs.Write(input_arry, 0, input_arry.Length);
                            cs.FlushFinalBlock();
                        }
                        return "?ecv=" + _server.UrlEncode(Convert.ToBase64String(ms.ToArray()));
                    }
                }
            }
            catch
            {
                return "?ecv=error";
            }
        }
        /// <summary>
        /// Encrypts a query string given the string format. String can either include or leave out the ? at the beginning. Value returned will be prepended with ?ecv
        /// </summary>
        /// <param name="queryString">The query string to encrypt.</param>
        /// <returns>The query string encrypted.</returns>
        public string EncryptQueryStringFormat(string queryFormatString, params object[] parameters)
        {
            return EncryptQueryString(string.Format(queryFormatString, parameters));
        }

        /// <summary>
        /// Encrypts the query string portion of the url, returning the url with the encrypted query string append to it.
        /// </summary>
        /// <param name="url">Url to encrypt.</param>
        /// <returns></returns>
        public string EncryptUrl(string url)
        {
            var idx = url.IndexOf('?');
            if (idx < 0)
                return url;

            return url.Substring(0, idx) + EncryptQueryString(url.Substring(idx));
        }


        /// <summary>
        /// Encrypts query string portion of a url string given the string format, returning the url with the encrypted query string append to it.
        /// </summary>
        /// <param name="url">Url to encrypt.</param>
        /// <returns></returns>
        public string EncryptUrlFormat(string url, params object[] parameters)
        {
            return EncryptUrl(string.Format(url, parameters));
        }

        /// <summary>
        /// Decrypts the query string
        /// </summary>
        private NameValueCollection DeCrypt(string qs)
        {
            //now split and fill the hash table
            NameValueCollection ht_enc_val = new NameValueCollection();

            try
            {
                string sOutput = DecryptString(qs.Replace(" ", "+"));
                if (sOutput.Length < 2)
                    return ht_enc_val;
                string[] val_ar = sOutput.Substring(1).Split('&');
                foreach (string s in val_ar)
                {
                    string[] tmp_ar = s.Split('=');
                    if (tmp_ar.Length == 2)
                    {
                        ht_enc_val.Add(tmp_ar[0], tmp_ar[1]);
                    }
                }
                return ht_enc_val;
            }
            catch
            {
                return null;
            }
        }
        /// <summary>
        /// Decrypts the query string
        /// </summary>
        public string DecryptString(string str)
        {
            try
            {
                byte[] bgkey = System.Text.Encoding.UTF8.GetBytes(GetEncryptionKey());
                byte[] input_arry = Convert.FromBase64String(str);

                using (DESCryptoServiceProvider des1 = new DESCryptoServiceProvider())
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, des1.CreateDecryptor(bgkey, iv), CryptoStreamMode.Write))
                        {
                            cs.Write(input_arry, 0, input_arry.Length);
                            cs.FlushFinalBlock();
                        }

                        return Encoding.UTF8.GetString(ms.ToArray());
                    }
                }
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        private bool BuildEncryptionKeys()
        {
            _key = GenerateKey();

            return true;
        }

        /// <summary>
        /// Generates a key for user specific query string encryption.
        /// </summary>
        private string GenerateKey()
        {
            StringBuilder key = new StringBuilder();

            string pwd = _secmgr.UserID + _secmgr.UserName;

            if (pwd.Length == 0)
                pwd = "null";//have to have some value;

            int size = Math.Min(pwd.Length, key_length);
            for (int i = 0; i < key_length * 2 / size; i++)
                key.Append(pwd.Substring(0, size));

            if (key.Length > key_length)
                key.Length = key_length;
            return key.ToString();
        }

        private string GetEncryptionKey()
        {
            return _key.Substring(0, key_length);
        }
        #endregion
    }
}
